/************************************************************************
 *				SRLOS Team CopyRight
 *@file		process.c
 *@author	bloceanc
 *@date		7/17/2010
 *@note		process managerment
 ************************************************************************/
#include "mem_mmu_page.h"
#include "mem_config.h"
#include "commdef.h"
#include "os_config.h"
#include "task_config.h"
#include "task.h"

extern k_process *s_queue_process;	// process list in current system
extern k_process *s_wait_terminate; // process need terminated.
extern k_mutex g_process_mutex;	// process mutex


extern k_process *g_current_process;
extern k_thread *g_current_thread;


extern void *kernel_allocate(unsigned int size);
extern void kernel_free(void *address);

extern int mt_mutex_lock(k_mutex *mutex, long long time);
extern void mt_mutex_unlock(k_mutex *mutex);

extern void mem_physical_page_inc_ref(void *phy_page_addr);
extern void mem_physical_page_inc_fork(void *phy_page_addr);
extern void mem_physical_page_sub_fork(void *phy_page_addr);
extern void mem_physical_page_sub_ref(void *phy_page_addr);
extern void *mem_physical_page_allocate_flpt(void);

extern void *__mmu_os_virtual_allocate(unsigned int page_count);
extern void __mmu_os_vritual_free(unsigned int page_viraddr, unsigned int page_count);
extern void *mmu_main_thread_stack_allocate(k_process *process, unsigned int size);
extern void mem_mmu_free_process_memory(unsigned int *phy_flpt);
extern void mem_mmu_clear_zero(unsigned int *vir_addr, int page_count);

extern k_thread *task_createthread(unsigned int priority, unsigned int code_addr, unsigned int *usr_stack);
extern void task_tid_release(unsigned int id);
extern void task_thread_releasethread(k_thread *thread);

/*
 *  add new process to process list
 *  NOTE: we add this in order by current priority of process.
 *			from big to little!
 */
void task_insert_process(k_process *process)
{
	k_process *tmp = NULL;
	k_process *last = NULL;

	mt_mutex_lock(&g_process_mutex, -1);
	if(s_queue_process == NULL)
	{
		process->next = NULL;
		process->prev = NULL;
		s_queue_process = process;
		mt_mutex_unlock(&g_process_mutex);
		return;
	}

	tmp = s_queue_process;
	while(tmp)
	{
		if(tmp->cur_priority < process->cur_priority)
		{
			break;
		}
		last = tmp;
		tmp = tmp->next;
	}

	if(tmp)
	{
		// insert behead of tmp.
		process->next = tmp;
		if(tmp->prev == NULL)
		{
			// this is the first one in queue!
			tmp->prev = process;
			process->prev = NULL;
			s_queue_process = process;
		}
		else
		{
			tmp->prev->next = process;
			process->prev = tmp->prev;
			tmp->prev = process;
		}
	}
	else
	{
		// we need to insert the last. if s_queue_process is NULL, it will be return before. so need to insert to the last.
		process->next = NULL;
		process->prev = last;
		last->next = process;
	}

	mt_mutex_unlock(&g_process_mutex);
}

/*
 * kernel mode task stack copy 
 */
static void task_kernelstack_copy(unsigned int *src, unsigned int *dst)
{
	unsigned int i;
	unsigned int *cur_fp = NULL;
	unsigned int *dst_sp = NULL;
	unsigned int *dst_fp = NULL;
	unsigned int count = TASK_THREAD_SYS_STACK_SIZE >> 2;
	unsigned int stack_end = (unsigned int)src + TASK_THREAD_SYS_STACK_SIZE;
	unsigned int stack_pre_end = stack_end - 0x10 - 4;	// more 4 bytes for safe^_^
	for(i = 0; i < count; i++)
	{
		dst[i] = src[i];
	}
	
	// now, we have to modify stack frames...it should use new value coressboud the new stack address.
	__asm__ __volatile__("mov %0,fp\n\t":"=r"(cur_fp)::"memory");
	
	while((unsigned int)cur_fp)
	{
		dst_sp = (unsigned int *)((unsigned int)dst + ((unsigned int)cur_fp - 8) - (unsigned int)src);
		dst_fp = (unsigned int *)((unsigned int)dst + ((unsigned int)cur_fp - 12) - (unsigned int)src);
		*dst_sp = (unsigned int)dst + *(cur_fp - 2) - (unsigned int)src;
		*dst_fp = (unsigned int)dst + *(cur_fp - 3) - (unsigned int)src;
		cur_fp = (unsigned int *)*(cur_fp - 3);
	}
}

/*
 * redirect FP register
 */
static void task_process_fork_redirect_context(unsigned int *sp, unsigned int stack_src, unsigned int stack_dst)
{
	sp += 12;
	*sp = *sp - stack_src + stack_dst;
}

/*
 * page deal when fork new process
 * 		incrate physical page reference, and make all user mode virtual page readonly!
 * NOTE: read only must is user mode virtual page.otherwise, the process will can't share
 * 	the OS space with other processes, and we can't control it anymore.because it will create
 * new kernel mode page when access readonly page!!
 */
static void task_process_page_deal_inc(unsigned int *flpt)
{
	unsigned int vir_idx, i, k;
	unsigned int *vir_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)flpt));
	unsigned int slpt_used_flag = 0;	// indicate whether current SLPT is valid
	unsigned int *vir_sec_addr = NULL;
	unsigned int slpt_used = 0;
	
	// from user mode start, make readonly, and incrate reference
	vir_idx = K_CONFIG_USR_VIR_MEM_START >> 20;
	
	for(;vir_idx < 4096; vir_idx++)	// 4096? every flpt record mapping 1M space!
	{
		if(vir_flpt[vir_idx] & 0x3)
		{
			vir_sec_addr = (unsigned int *)(vir_flpt[vir_idx] & 0xfffffc00);
			vir_sec_addr = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)vir_sec_addr));
			
			// this is mappinged. we need check slpt
			for(i = 0; i < 256; i++)
			{
				if(vir_sec_addr[i]&0x3)
				{
					// we need deal this page, but how can we make this page readonly, including supervisitor mode,
					// because kernel will access the space and may need it create new physical page.so, when kernel
					// access these pages, they should be readonly also!!!!!!
					
					// make read only
					vir_sec_addr[i] &= MMU_PAGE_AP_READONLY_MASK;
					// incrate use references
					mem_physical_page_inc_ref((void *)(vir_sec_addr[i]&0xfffff000));
					// incrate fork references
					mem_physical_page_inc_fork((void *)(vir_sec_addr[i]&0xfffff000));
					

				}
			}
			
			// set flag, indicate that this SLPT physical page is in used!
			slpt_used_flag = 1;
			
			// save SLPT address in used!
			slpt_used = (unsigned int)vir_sec_addr;
		}
		
		// WARNNING:
		// every physical page contains 4 FLPT records, and SLPT is allocated in physical page unit.
		// so, continuous 4 FLPT records use the same physical page.
		if((vir_idx&0x3)==0x3 && slpt_used_flag )		// WARNNING: Here we must compare to value 0x3, this means this is the last one in continuous 4
		{
			// continuous 4 FLPT records and physical page in using . so:
			slpt_used_flag = 0;
			// we increate the fork reference of the current SLPT, but we still make it writable!
			// when readonly abort occured, we first check whether need to allocate new SLPT for
			// process by fork reference, the newest slpt will take fork reference attibute valute
			// zero, so, we can recorgnize whether need   to allocate new SLPT( whether this SLPT is
			// owen by process)
			mem_physical_page_inc_fork((void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY((unsigned int)slpt_used)));
			// Also, we have better to inc the common reference, then, we can terminate process anytime
			// but don't careabout the problems about SLPT share!^_^
			mem_physical_page_inc_ref((void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY((unsigned int)slpt_used)));
		}
	}
}

/*
 * page deal when fork new application
 * 		incrate physical page reference, and make all user mode virtual page readonly!
 * NOTE: read only must is user mode virtual page.otherwise, the process will can't share
 * 	the OS space with other processes, and we can't control it anymore.because it will create
 * new kernel mode page when access readonly page!!
 */
static void task_process_page_deal_dec(unsigned int *flpt)
{
	unsigned int vir_idx, i, k;
	unsigned int *vir_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)flpt));
	unsigned int slpt_used_flag = 0;	// indicate whether current SLPT is valid
	unsigned int *vir_sec_addr = NULL;
	unsigned int slpt_used = 0;
	
	// from user mode start, make readonly, and incrate reference
	vir_idx = K_CONFIG_USR_VIR_MEM_START >> 20;
	
	for(;vir_idx < 4096; vir_idx++)	// 4096? every flpt record mapping 1M space!
	{
		if(vir_flpt[vir_idx] & 0x3)
		{
			vir_sec_addr = (unsigned int *)(vir_flpt[vir_idx] & 0xfffffc00);
			vir_sec_addr = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)vir_sec_addr));
			
			// this is mappinged. we need check slpt
			for(i = 0; i < 256; i++)
			{
				if(vir_sec_addr[i]&0x3)
				{
					// we need deal this page, but how can we make this page readonly, including supervisitor mode,
					// because kernel will access the space and may need it create new physical page.so, when kernel
					// access these pages, they should be readonly also!!!!!!
					
					// make read only
					vir_sec_addr[i] &= MMU_PAGE_AP_READONLY_MASK;
					// incrate use references
					mem_physical_page_sub_ref((void *)(vir_sec_addr[i]&0xfffff000));
					// incrate fork references
					mem_physical_page_sub_fork((void *)(vir_sec_addr[i]&0xfffff000));
				}
			}
			
			// set flag, indicate that this SLPT physical page is in used!
			slpt_used_flag = 1;
			
			// save SLPT address in used!
			slpt_used = (unsigned int)vir_sec_addr;

		}
		
		// WARNNING:
		// every physical page contains 4 FLPT records, and SLPT is allocated in physical page unit.
		// so, continuous 4 FLPT records use the same physical page.
		if((vir_idx&0x3)==0x3 && slpt_used_flag )		// WARNNING: Here we must compare to value 0x3, this means this is the last one in continuous 4
		{
			// continuous 4 FLPT records and physical page in using . so:
			slpt_used_flag = 0;
			
			// we increate the fork reference of the current SLPT, but we still make it writable!
			// when readonly abort occured, we first check whether need to allocate new SLPT for
			// process by fork reference, the newest slpt will take fork reference attibute valute
			// zero, so, we can recorgnize whether need to allocate new SLPT( whether this SLPT is
			// owen by process)
			mem_physical_page_sub_fork((void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY((unsigned int)slpt_used)));
			// Also, we have better to inc the common reference, then, we can terminate process anytime
			// but don't careabout the problems about SLPT share!^_^
			mem_physical_page_sub_ref((void *)(COMM_MMU_SYSTEMPAGE_ADDRESS_VIR2PHY((unsigned int)slpt_used)));
		}
	}
}

/*
 * copy page table.only first level
 * 	NOTE: All parameters are physical address!
 */
void task_process_flpt_copy(unsigned int *src_flpt, unsigned int *dst_flpt)
{
	int count = TASK_MMU_FLPT_SIZE >> 2;	// 16K/4  ^_^
	int i;
	src_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)src_flpt));
	dst_flpt = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(((unsigned int)dst_flpt));
	for(i = 0; i < count; i++)
	{
		dst_flpt[i] = src_flpt[i];
	}
}

/*
 * kernel PCB initialize
 */
void task_pcb_initialize(k_process *pcb)
{
	if(pcb == NULL)
		return;
	pcb->mem.mmu.pt_phy_address = 0;
	pcb->mem.mmu.cache_state = 0;
	pcb->mem.code_end = 0;
	pcb->mem.brk_end = 0;
	pcb->mem.stack_start = 0xffffffff;

	// thread mutex initialize
	mt_mutex_initset(&pcb->thread_mutex);
	pcb->pid = 0;
	pcb->thread_running = NULL;
	pcb->thread_runable = NULL;
	pcb->thread_suspend = NULL;
	pcb->thread_waitting = NULL;
	pcb->thread_alarm = NULL;
	pcb->thread_terminating = NULL;
	pcb->parent = NULL;
	pcb->runable_thread_count = 0;
	pcb->cur_priority = 0;
	pcb->fix_priority = 0;
	
	pcb->next = NULL;
	pcb->prev = NULL;
}

/*
 * fork
 *	we do this as the linux do it better.
 *		return 0 as child process, >0 as child id, so this is parent process 
 */
int task_process_fork(void)
{
	k_process *pcb = NULL;
	k_thread *main = NULL;
	int ischild = 1;
	int childid = 0;
	unsigned int resume_addr = (unsigned int)(&&children);
	
	pcb = kernel_allocate(sizeof(k_process));
	if(pcb == NULL)
		return -1;
	
	// initialize pcb!
	task_pcb_initialize(pcb);
	
	// allocate new FLPT.This is physical address!!!!!!! remember it!!!
	pcb->mem.mmu.pt_phy_address = (unsigned int)mem_physical_page_allocate_flpt();

	// we should check first and then change it into physical address^_^
	if(pcb->mem.mmu.pt_phy_address == 0)
	{
		kernel_free(pcb);
		return -1;
	}
	else
	{
		// we must need clear the new FLPT
		mem_mmu_clear_zero((unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR((pcb->mem.mmu.pt_phy_address)), 4);
		
		// we just use NULL as user mode stack, infact, this stack has the same virtual address with current parent thread of its
		// parent, and will use the same address until modify access accured on this stack, then we will create a new physical stack 
		// for child or parent thread.at that time, they have different stack but still have the same virtual stack address.^_^
		main = task_createthread(TASK_PRIORITY_NORMAL, NULL, NULL);	// we just use create. stack don't care. we will copy from parent^_^
	}
	if(main == NULL)
	{
		kernel_free(pcb);
		__mmu_os_vritual_free(COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(pcb->mem.mmu.pt_phy_address), 4);
		return -3;
	}

	// modify thread informations
	main->process = pcb;
	main->usr_stack = g_current_thread->usr_stack; // them stack are same in virtual address because one copyed anothor
	main->type = TASK_THREAD_TYPE_MAIN;
	main->id = task_tid(main); 
	main->state = TASK_THREAD_STATE_RUNABLE;
	main->fix_priority = g_current_thread->fix_priority;
	main->cur_priority = g_current_thread->cur_priority;

	// insert this main thread into its process
	// there only one thread in new process. it's main thread. so we just make the runable thread pointer the thread.
	pcb->thread_runable = main;
	pcb->parent = g_current_process;
	pcb->fix_priority = g_current_process->fix_priority;
	pcb->cur_priority = g_current_process->cur_priority;

	// save current cpu context 
	TASK_CPU_CONTEXT_SAVE(g_current_thread, resume_addr);

	// now copy the kernel stack to new thread of new process.
	// this must down after save.because we need the CPU context. then it saved in kernel stack^_^
	task_kernelstack_copy((unsigned int *)g_current_thread->sys_stack, (unsigned int *)main->sys_stack);
	// set top pointer. when resume it will use!
	main->kernel_sp = main->sys_stack + (g_current_thread->kernel_sp - g_current_thread->sys_stack);
	
	// we must adjust FP register pointing corressbouding with new stack like stack copy doning!!!!
	task_process_fork_redirect_context((unsigned int *)main->kernel_sp, g_current_thread->sys_stack, main->sys_stack);
	
	// after copy stack, we change the flag^_^.then the children has a new kernel stack, but its value 
	// is not changed.then we can recogenize which is children and which is parent!^_^
	// must do after stack copyed!^_^
	ischild = 0;
	
	// copy flpt!
	task_process_flpt_copy((unsigned int *)g_current_process->mem.mmu.pt_phy_address, (unsigned int *)pcb->mem.mmu.pt_phy_address);
	
	// increat the reference count of using physical page, and make them read only!
	task_process_page_deal_inc((unsigned int *)g_current_process->mem.mmu.pt_phy_address);
	
	// get new process id
	childid = task_pid();
	pcb->pid = childid;
	// insert new process to process list
	task_insert_process(pcb);
	
	// resume thread of parent process
	TASK_CPU_CONTEXT_RESUME(g_current_thread);
	
children:
	if(ischild)
		return 0;
	return childid;
}

/*
 * modify system mode stack for new application
 */
void s_process_sys_stack_deal(unsigned int *bottom_sys_stack, void *code_addr, unsigned int top_usr_stack, unsigned int cpsr)
{
	unsigned int i = 0;
	*(--bottom_sys_stack) = (unsigned int)code_addr;	// r14 for interrupt return.
	*(--bottom_sys_stack) = cpsr;		// cpsr...
	bottom_sys_stack += 13;	// R0 ~ R12
	*(--bottom_sys_stack) = top_usr_stack;	// R13_usr
}

/*
 *	create process with new application
 */
int task_create_process(char *app_path, char *cmdline, int mode)
{
	int pid;

	// first used fork function create a new process, a copy of current process
	pid = task_process_fork();
	if(pid == 0)
	{
		unsigned int load_address = 0;
		// child process has been running now!
		
		// load application, when load this, we don't need to allocate memory, unpresent page exception will
		// do allocating!^_^, so just load!^_^!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

//===========> need define this <==============//
//		load_address = task_load_application(char *app_path, int mode);
		
		// we will never use memory space of parent process. so, we should reduce the reference about memory.
		// after this, we will use a newest memory space.$_$
		task_process_page_deal_dec((unsigned int *)g_current_process->mem.mmu.pt_phy_address);
// 这里有严重的问题，到时候需要详细的考虑一下，好好的修改一下！！！！		
		// create a usr_mode stack for main thread.
		g_current_thread->usr_stack = (unsigned int)mmu_main_thread_stack_allocate(g_current_process, K_CONFIG_USR_MAIN_THREAD_STACK_SIZE);
		
		// need modify return address and user_mode stack top address in interrupt stack data area, because when this 
		// return user mode from interrupt, it need jump to address code loaded.
		s_process_sys_stack_deal((unsigned int *)(g_current_thread->sys_stack+TASK_THREAD_SYS_STACK_SIZE), (unsigned int *)load_address, 
				(g_current_thread->usr_stack + K_CONFIG_USR_MAIN_THREAD_STACK_SIZE), 0x1f);
	
	}
	else
	{
		// parent process, now we do nothing. just return pid of child process
		return pid;
	}
}

/*
 * move PCB from normal list to terminate list 
 */
static s_task_process_move_terminate(k_process *process)
{
	if(process == NULL)
		return;

	if(process->state == TASK_PROCESS_STATE_TERMINATE)
	{
		// has been terminated
		return;
	}
	
	mt_mutex_lock(&g_process_mutex, -1);
	if(process->next == NULL && process->prev == NULL)
	{
		// only one 
		s_queue_process = NULL;
		// Infact, this is impossible, so there must be some errors!
		return;
	}
	if(process->next == NULL && process->prev != NULL)
	{
		// the last one
		process->prev->next = NULL;
	}
	if(process->next != NULL && process->prev == NULL)
	{
		// the first one
		s_queue_process = s_queue_process->next;
		s_queue_process->prev = NULL;
	}
	if(process->next != NULL && process->prev != NULL)
	{
		// normal
		process->prev->next = process->next;
		process->next->prev = process->prev;
	}
	process->prev = NULL;
	process->next = NULL;

	// insert into terminate queue
	if(s_wait_terminate == NULL)
		s_wait_terminate = process;
	else
	{
		s_wait_terminate->prev = process;
		process->next = s_wait_terminate;
		s_wait_terminate = process;
	}
	mt_mutex_unlock(&g_process_mutex);
}

/*
 * terminate process
 *@RETURN: Exit Code
 */
void task_process_terminate(k_process *process)
{
	// move process from queue to terminate list
	// after a new schedule, we will free PCB!
	s_task_process_move_terminate(process);
	
	if(g_current_process == process)
		task_schedule(NULL);
}

/*
 * release process
 */
static void task_process_release(k_process *pcb)
{
	k_thread *tmp = NULL;
	k_thread *thread = NULL;

	if(pcb == NULL)
	{
		return;
	}
	
	// release resouces process used.
	


	// free user space memorys
	mem_mmu_free_process_memory((unsigned int *)pcb->mem.mmu.pt_phy_address);
	
	// free kernel space memorys
	/* 1.free all thread memory.TCB memory!*/
	tmp = pcb->thread_running;
	if(tmp)
		task_thread_releasethread(tmp);
	tmp = pcb->thread_runable;
	while(tmp)
	{
		thread = tmp->next;
		task_thread_releasethread(tmp);
		tmp = thread;
	}
	
	tmp = pcb->thread_suspend;
	while(tmp)
	{
		thread = tmp->next;
		task_thread_releasethread(tmp);
		tmp = thread;
	}
	
	tmp = pcb->thread_waitting;
	while(tmp)
	{
		thread = tmp->next;
		task_thread_releasethread(tmp);
		tmp = thread;
	}
	
	tmp = pcb->thread_terminating;
	while(tmp)
	{
		thread = tmp->next;
		task_thread_releasethread(tmp);
		tmp = thread;
	}

	task_tid_release(pcb->pid);
	
	// free FLPT
	__mmu_os_vritual_free(COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(pcb->mem.mmu.pt_phy_address), 4);
	
	kernel_free(pcb);
}

/*
 *	real terminate process.clear terminate process list
 * NOTE: This function need caller use global lock protection!
 * WARNING:this function only can be called in task_schedule!
 */
void task_process_terminate_handler(void)
{
	k_process *process = NULL;
	
	process = s_wait_terminate;
	if(process)
		s_wait_terminate = process->next;
	while(process)
	{
		task_process_release(process);
		process = s_wait_terminate;
		if(process)
			s_wait_terminate = process->next;
	}
}

/*
 * create default system process
 *NOTE: Any other cann't call this function. this just used to create system default process.
 */
void task_process_create_sysdefault(k_thread *thread)
{
	k_process *pro = kernel_allocate(sizeof(k_process));
	unsigned int i;
	unsigned int sys_flpt_count = MEM_CONFIG_SYS_SPACE_SIZE >> 20;	// ervery count mapping 1MB, and it is 4byte^_^
	unsigned int *sys_vir_flpt_start = (unsigned int *)0x00104000;
	unsigned int *dst_vir_flpt_start = NULL;
	
	if(pro == NULL)
	{
		// we just loop system , but can't do nothing...
		while(1);
	}
	task_pcb_initialize(pro);
	
	// we need allocate new flpt, for new process copy using!^_^^_^
	pro->mem.mmu.pt_phy_address = (unsigned int)mem_physical_page_allocate_flpt();
	if(pro->mem.mmu.pt_phy_address == 0)
	{
		// loop down system.....
		while(1);
	}	
	
	// we need copy system flpt!
	dst_vir_flpt_start = (unsigned int *)COMM_MMU_SYSTEMPAGE_ADDRESS_PHY2VIR(pro->mem.mmu.pt_phy_address);
	
	// zero page
	mem_mmu_clear_zero(dst_vir_flpt_start, 4);
	
	for(i = 0; i < sys_flpt_count; i++)
	{
		dst_vir_flpt_start[i] = sys_vir_flpt_start[i];
	}

	pro->thread_runable = thread;
	thread->process = pro;
	thread->state = TASK_THREAD_STATE_RUNABLE;
	task_insert_process(pro);
}

